/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/*
 * RawSignalPanel.java
 *
 * Created on 1-jan-2011, 18:10:27
 */
package nl.lemval.nododue.tabs;

import java.awt.Cursor;
import java.util.Arrays;
import nl.lemval.nododue.NodoDueManager;
import nl.lemval.nododue.NodoDueManagerView;
import nl.lemval.nododue.cmd.CommandInfo;
import nl.lemval.nododue.cmd.CommandInfo.Name;
import nl.lemval.nododue.cmd.CommandLoader;
import nl.lemval.nododue.cmd.NodoCommand;
import nl.lemval.nododue.cmd.NodoResponse;
import nl.lemval.nododue.component.RawSignalGraphPanel;
import nl.lemval.nododue.util.SerialCommunicator;
import nl.lemval.nododue.util.listeners.OutputEventListener;
import org.jdesktop.application.Action;

/**
 *
 * @author Michael
 */
public class RawSignalPanel extends NodoBasePanel {

    private double dividerValue;
    private int thresholdValue;
    private static final double[] DIVIDERS = {
        0.5, 0.6, 0.7, 0.8, 0.9, 1, 1.25, 
        1.5, 1.75, 2, 2.5, 3, 4, 5, 7.5, 10
    };
    private int[] backupSignal = null;

    /** Creates new form RawSignalPanel */
    public RawSignalPanel(final NodoDueManagerView view) {
        super(view);
        initComponents();

    }

    @Override
    public void setVisible(boolean aFlag) {
        super.setVisible(aFlag);
        if (aFlag) {
            initStatus();
        }
    }

    private static StringBuilder buildRawString(int[] activeSignal) {
        StringBuilder data = new StringBuilder();
        data.append(CommandInfo.Name.RawsignalPut.name());
        data.append(';');
        int size = 0;
        for (int i = 0; i < activeSignal.length; i++) {
            int value = activeSignal[i];
            if (value == 0) {
                break;
            }
            data.append("0x");
            data.append(Integer.toHexString(value).toUpperCase());
            data.append(',');
            size++;
        }
        // Close with 0, need at least even number without 0
        if (size % 2 == 1) {
            data.append("0x0,0x0;");
        } else {
            data.append("0x0;");
        }
        return data;
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        lineGraph = new RawSignalGraphPanel();
        nameLabel = new javax.swing.JLabel();
        buttonPanel = new javax.swing.JPanel();
        retrieveButton = new javax.swing.JButton();
        cleanButton = new javax.swing.JButton();
        sendButton = new javax.swing.JButton();
        startSlider = new javax.swing.JSlider();
        endSlider = new javax.swing.JSlider();
        rawSignalText = new javax.swing.JTextField();
        valueLabel = new javax.swing.JLabel();
        invert = new javax.swing.JCheckBox();
        jPanel1 = new javax.swing.JPanel();
        divider = new javax.swing.JSlider();
        dividerText = new javax.swing.JLabel();
        jPanel2 = new javax.swing.JPanel();
        threshold = new javax.swing.JSlider();
        thresholdText = new javax.swing.JLabel();

        setName("Form"); // NOI18N

        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(nl.lemval.nododue.NodoDueManager.class).getContext().getResourceMap(RawSignalPanel.class);
        lineGraph.setBackground(resourceMap.getColor("lineGraph.background")); // NOI18N
        lineGraph.setBorder(javax.swing.BorderFactory.createLineBorder(resourceMap.getColor("lineGraph.border.lineColor"))); // NOI18N
        lineGraph.setMaximumSize(new java.awt.Dimension(250, 50));
        lineGraph.setMinimumSize(new java.awt.Dimension(250, 50));
        lineGraph.setName("lineGraph"); // NOI18N

        javax.swing.GroupLayout lineGraphLayout = new javax.swing.GroupLayout(lineGraph);
        lineGraph.setLayout(lineGraphLayout);
        lineGraphLayout.setHorizontalGroup(
            lineGraphLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 539, Short.MAX_VALUE)
        );
        lineGraphLayout.setVerticalGroup(
            lineGraphLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 48, Short.MAX_VALUE)
        );

        nameLabel.setFont(nameLabel.getFont().deriveFont(nameLabel.getFont().getStyle() | java.awt.Font.BOLD, nameLabel.getFont().getSize()+1));
        nameLabel.setText(resourceMap.getString("nameLabel.text")); // NOI18N
        nameLabel.setName("nameLabel"); // NOI18N

        buttonPanel.setName("buttonPanel"); // NOI18N

        javax.swing.ActionMap actionMap = org.jdesktop.application.Application.getInstance(nl.lemval.nododue.NodoDueManager.class).getContext().getActionMap(RawSignalPanel.class, this);
        retrieveButton.setAction(actionMap.get("retrieveRawSignal")); // NOI18N
        retrieveButton.setText(resourceMap.getString("retrieveButton.text")); // NOI18N
        retrieveButton.setName("retrieveButton"); // NOI18N
        buttonPanel.add(retrieveButton);

        cleanButton.setAction(actionMap.get("cleanRawSignal")); // NOI18N
        cleanButton.setText(resourceMap.getString("cleanButton.text")); // NOI18N
        cleanButton.setToolTipText(resourceMap.getString("cleanButton.toolTipText")); // NOI18N
        cleanButton.setName("cleanButton"); // NOI18N
        buttonPanel.add(cleanButton);

        sendButton.setAction(actionMap.get("sendRawSignal")); // NOI18N
        sendButton.setText(resourceMap.getString("sendButton.text")); // NOI18N
        sendButton.setName("sendButton"); // NOI18N
        buttonPanel.add(sendButton);

        startSlider.setPaintLabels(true);
        startSlider.setValue(0);
        startSlider.setName("startSlider"); // NOI18N
        startSlider.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                startPointStateChanged(evt);
            }
        });

        endSlider.setValue(0);
        endSlider.setName("endSlider"); // NOI18N
        endSlider.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                endPointStateChanged(evt);
            }
        });

        rawSignalText.setEditable(false);
        rawSignalText.setText(resourceMap.getString("rawSignalText.text")); // NOI18N
        rawSignalText.setName("rawSignalText"); // NOI18N

        valueLabel.setText(resourceMap.getString("valueLabel.text")); // NOI18N
        valueLabel.setName("valueLabel"); // NOI18N

        invert.setAction(actionMap.get("invertSignal")); // NOI18N
        invert.setText(resourceMap.getString("invert.text")); // NOI18N
        invert.setName("invert"); // NOI18N

        jPanel1.setBorder(javax.swing.BorderFactory.createTitledBorder(resourceMap.getString("jPanel1.border.title"))); // NOI18N
        jPanel1.setName("jPanel1"); // NOI18N
        jPanel1.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT));

        divider.setMajorTickSpacing(1);
        divider.setMaximum(15);
        divider.setMinorTickSpacing(1);
        divider.setPaintTicks(true);
        divider.setSnapToTicks(true);
        divider.setToolTipText(resourceMap.getString("divider.toolTipText")); // NOI18N
        divider.setValue(6);
        divider.setName("divider"); // NOI18N
        divider.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                dividerStateChanged(evt);
            }
        });
        jPanel1.add(divider);

        dividerText.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        dividerText.setText(resourceMap.getString("dividerText.text")); // NOI18N
        dividerText.setMaximumSize(new java.awt.Dimension(100, 30));
        dividerText.setMinimumSize(new java.awt.Dimension(50, 16));
        dividerText.setName("dividerText"); // NOI18N
        dividerText.setPreferredSize(new java.awt.Dimension(50, 16));
        jPanel1.add(dividerText);

        jPanel2.setBorder(javax.swing.BorderFactory.createTitledBorder(resourceMap.getString("jPanel2.border.title"))); // NOI18N
        jPanel2.setName("jPanel2"); // NOI18N
        jPanel2.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.LEFT));

        threshold.setMajorTickSpacing(1);
        threshold.setMaximum(20);
        threshold.setMinimum(2);
        threshold.setMinorTickSpacing(1);
        threshold.setPaintTicks(true);
        threshold.setSnapToTicks(true);
        threshold.setToolTipText(resourceMap.getString("threshold.toolTipText")); // NOI18N
        threshold.setValue(2);
        threshold.setName("threshold"); // NOI18N
        threshold.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                thresholdStateChanged(evt);
            }
        });
        jPanel2.add(threshold);

        thresholdText.setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
        thresholdText.setText(resourceMap.getString("thresholdText.text")); // NOI18N
        thresholdText.setMinimumSize(new java.awt.Dimension(50, 16));
        thresholdText.setName("thresholdText"); // NOI18N
        thresholdText.setPreferredSize(new java.awt.Dimension(50, 16));
        jPanel2.add(thresholdText);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(buttonPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 541, Short.MAX_VALUE)
                    .addComponent(lineGraph, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(nameLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 131, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(startSlider, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 541, Short.MAX_VALUE)
                    .addComponent(endSlider, javax.swing.GroupLayout.DEFAULT_SIZE, 541, Short.MAX_VALUE)
                    .addComponent(valueLabel)
                    .addComponent(rawSignalText, javax.swing.GroupLayout.DEFAULT_SIZE, 541, Short.MAX_VALUE)
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addComponent(invert)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 138, Short.MAX_VALUE)
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                            .addComponent(jPanel2, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                            .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(nameLabel)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(lineGraph, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(startSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(endSlider, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(valueLabel)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(rawSignalText, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(invert)
                    .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 38, Short.MAX_VALUE)
                .addComponent(buttonPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap())
        );

        jPanel1.getAccessibleContext().setAccessibleName(resourceMap.getString("jPanel1.AccessibleContext.accessibleName")); // NOI18N
    }// </editor-fold>//GEN-END:initComponents

    private void startPointStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_startPointStateChanged
        if (startSlider.getValueIsAdjusting()) {
            int start = startSlider.getValue();
            int end = endSlider.getValue();
            if (start > end) {
                endSlider.setValue(start);
                end = start;
            }
            ((RawSignalGraphPanel) lineGraph).setBounds(start, end);
            ((RawSignalGraphPanel) lineGraph).repaint();
            updateSignalText();
        }
    }//GEN-LAST:event_startPointStateChanged

    private void endPointStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_endPointStateChanged
        if (endSlider.getValueIsAdjusting()) {
            int start = startSlider.getValue();
            int end = endSlider.getValue();
            if (end < start) {
                startSlider.setValue(end);
                start = end;
            }
            ((RawSignalGraphPanel) lineGraph).setBounds(start, end);
            ((RawSignalGraphPanel) lineGraph).repaint();
            updateSignalText();
        }
    }//GEN-LAST:event_endPointStateChanged

    private void dividerStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_dividerStateChanged
        if (divider.getValueIsAdjusting()) {
            updateDividerValue();
        }
    }//GEN-LAST:event_dividerStateChanged

    private void thresholdStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_thresholdStateChanged
        if (threshold.getValueIsAdjusting()) {
            updateThresholdValue();
        }
    }//GEN-LAST:event_thresholdStateChanged

    private void updateDividerValue() {
        int value = divider.getValue();
        dividerValue = DIVIDERS[value];
        dividerText.setText(""+dividerValue);
    }

    private void updateThresholdValue() {
        thresholdValue = threshold.getValue();
        thresholdText.setText("" + thresholdValue);
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JPanel buttonPanel;
    private javax.swing.JButton cleanButton;
    private javax.swing.JSlider divider;
    private javax.swing.JLabel dividerText;
    private javax.swing.JSlider endSlider;
    private javax.swing.JCheckBox invert;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPanel jPanel2;
    private javax.swing.JPanel lineGraph;
    private javax.swing.JLabel nameLabel;
    private javax.swing.JTextField rawSignalText;
    private javax.swing.JButton retrieveButton;
    private javax.swing.JButton sendButton;
    private javax.swing.JSlider startSlider;
    private javax.swing.JSlider threshold;
    private javax.swing.JLabel thresholdText;
    private javax.swing.JLabel valueLabel;
    // End of variables declaration//GEN-END:variables

    private void initStatus() {
        RawSignalGraphPanel graph = (RawSignalGraphPanel) lineGraph;
        // Initialize the sliders and the graph
        int width = graph.getWidth();
        int interval = width / 25;

        startSlider.setMinimum(0);
        startSlider.setMaximum(width);
        endSlider.setMajorTickSpacing(interval);
        endSlider.setMinorTickSpacing(1);

        endSlider.setMinimum(0);
        endSlider.setMaximum(width);
        endSlider.setValue(width);
        endSlider.setMajorTickSpacing(interval);
        endSlider.setMinorTickSpacing(1);

        updateDividerValue();
        updateThresholdValue();

        graph.setBounds(0, width);
//	if (NodoDueManager.isLinux()) {
//	    startSlider.setPaintLabels(true);
//	    endSlider.setPaintLabels(true);
//	}
    }

    @Action
    public void retrieveRawSignal() {
        // Clean the screen
        int[] result = new int[0];
        update(result);
        backupSignal = null;

       if (NodoDueManager.hasConnection()) {
            SerialCommunicator comm =
                    NodoDueManager.getApplication().getSerialCommunicator();
            final StringBuilder data = new StringBuilder();
            OutputEventListener lix = new OutputEventListener()  {

                public void handleOutputLine(String message) {
                    int idx = message.indexOf("RawSignal");
                    if ( idx >= 0 ) {
                        idx = message.indexOf('=', idx);
                        data.append(message.substring(idx+1));
                    }
                }

                public void handleClear() {
                }

                public void handleNodoResponses(NodoResponse[] responses) {
                }
            };
            comm.addOutputListener(lix);
            long t = System.currentTimeMillis();
            long delta = 0;
            try {
                CommandInfo ci = CommandLoader.get(Name.RawsignalGet);
                setCursor(new Cursor(Cursor.WAIT_CURSOR));
                comm.send(new NodoCommand(ci, null, null));
                do {
                    comm.waitCommand(200);
                    delta = System.currentTimeMillis() - t;
                } while ( data.length() == 0 && delta < 5000 );
                comm.removeOutputListener(lix);
            } catch (Exception e) {
                getListener().showStatusMessage(getResourceString("update_fail.margin", e.getMessage()));
            }
            setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
            
            String[] split = data.toString().split(", ?");
            int[] values = new int[split.length];
            int counter = 0;
            for (String aValue : split) {
                int value = -1;
                try {
                    if ( aValue.startsWith("0x") || aValue.startsWith("0X") ) {
                        value = Integer.parseInt(aValue.substring(2), 16);
                    } else {
                        value = Integer.parseInt(aValue);
                    }
                } catch (NumberFormatException e) {
                }
                if (value >= 0) {
                    values[counter++] = value;
                }
            }
            result = new int[counter];
            System.arraycopy(values, 0, result, 0, counter);
            update(result);
        }
    }

    @Action
    public void cleanRawSignal() {
        RawSignalGraphPanel graph = (RawSignalGraphPanel) lineGraph;
        int[] signal = graph.getSignal();
        if (dividerValue == 0 || signal == null || signal.length == 0) {
            return;
        }
        if ( backupSignal != null ) {
            signal = new int[backupSignal.length];
            System.arraycopy(backupSignal, 0, signal, 0, backupSignal.length);
        } else if ( signal != null ) {
            backupSignal = new int[signal.length];
            System.arraycopy(signal, 0, backupSignal, 0, signal.length);
        }

        // Find smallest value and divide by 3
        int smallest = Integer.MAX_VALUE;
        for (int i = 0; i < signal.length; i++) {
            int value = signal[i];
            // Values smaller than MIN will result in the same signal as before
            if (value < smallest && value >= thresholdValue) {
                smallest = value;
            }
        }
        smallest = (int) (smallest / dividerValue);
        if (smallest > 1) {
            for (int i = 0; i < signal.length; i++) {
                // Round value to a multiple of the smalest
                signal[i] = smallest * ((signal[i] + smallest / 2) / smallest);
            }
            update(signal);
        }
    }

    @Action
    public void sendRawSignal() {
        if (NodoDueManager.hasConnection()) {
            RawSignalGraphPanel graph = (RawSignalGraphPanel) lineGraph;
            int[] activeSignal = graph.getActiveSignal();
            StringBuilder data = buildRawString(activeSignal);

            SerialCommunicator comm =
                    NodoDueManager.getApplication().getSerialCommunicator();
            try {
                comm.sendRaw(data.toString());
                comm.waitCommand(1500, 2500);
            } catch (Exception e) {
                getListener().showStatusMessage(getResourceString("update_fail.margin", e.getMessage()));
            }
        }
    }

    private void update(int[] data) {
        RawSignalGraphPanel graph = (RawSignalGraphPanel) lineGraph;
        graph.setSignal(data);
        updateSignalText();
        graph.invalidate();
        graph.repaint();
    }

    private void updateSignalText() {
        RawSignalGraphPanel graph = (RawSignalGraphPanel) lineGraph;
        int[] activeSignal = graph.getActiveSignal();
        rawSignalText.setText(Arrays.toString(activeSignal));
    }

    @Action
    public void invertSignal() {
        ((RawSignalGraphPanel) lineGraph).setStartState(!invert.isSelected());
        lineGraph.repaint();
    }
}
